﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Linq;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Caching;
using Dimok;
using Dimok.Exceptions;
using Dimok.Shared;
using Dimok.Web.Filtering;
namespace Dimok.Web {

	#region IWcfGeneric ===================

  ////////////////////////////////////////////////////////////////////////////////////////////////////
  /// <summary>	Interface for wcf generic. </summary>
  ///
  /// <remarks>	Dimon, 12/21/2010. </remarks>
  ////////////////////////////////////////////////////////////////////////////////////////////////////

	[ServiceContract(Namespace = "")]
  public interface IWcfGeneric {
		[OperationContract]
		void SaveLayout(string PageType, string ControlID, ReportProfile Prifile, out FaultContract Fault);
		[OperationContract]
		ReportProfile GetProfile(string PageType, string ControlID, string ReportName, out Dimok.Shared.FaultContract Fault);
		[OperationContract]
		void RemoveProfile(string PageType, string ControlID, string ReportName, out Dimok.Shared.FaultContract Fault);
		[OperationContract]
		string GetDataSet(string EntityType, string[] Columns, string PageType, string ControlID, string ReportName,
			Dictionary<string, string> SortExpressions, Dictionary<string, string> Filters, out FaultContract Fault);
	}
	#endregion

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>	Wcf generic. </summary>
	///
	/// <remarks>	Dimon, 12/21/2010. </remarks>
	////////////////////////////////////////////////////////////////////////////////////////////////////

	public abstract class WcfGeneric<TDataContext,TProfiler,TMetaTablesData> : IWcfGeneric
		where TDataContext : DataContext, new()
		where TProfiler : IProfiler, new()
    where TMetaTablesData:MetaTablesDictionary,new()
  {
    #region Generics

		#region GetDataSet =================

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a data set. </summary>
		///
		/// <param name="EntityType">				Type of the entity. </param>
		/// <param name="Columns">					The columns. </param>
		/// <param name="PageType">					Type of the page. </param>
		/// <param name="ControlID">				Identifier for the control. </param>
		/// <param name="ReportName">				Name of the report. </param>
		/// <param name="SortExpressions">	The sort expressions. </param>
		/// <param name="Filters">					The filters. </param>
		/// <param name="Fault">						[out] The fault. </param>
		///
		/// <returns>	The data set. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public string GetDataSet(string EntityType,
 string[] Columns, string PageType, string ControlID, string ReportName,
Dictionary<string, string> SortExpressions,
Dictionary<string, string> Filters, out Dimok.Shared.FaultContract Fault) {
			try {
				var ds = GetDataSetObject(EntityType,
					Columns, PageType, ControlID, ReportName, SortExpressions, Filters, 0, Filters.Trim().Count == 0 ? 0 : -1, out Fault);
				if (ReportName == ReportProfile.EXCEL_REPORT_NAME) {
					var guid = Guid.NewGuid().ToString();
					System.Web.HttpContext.Current.Cache.Add(guid, ds, null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(2), CacheItemPriority.Default, null);
					return guid;
				}

				SaveLayout(PageType, ControlID,
					new ReportProfile(ReportName) { Filters = Filters, Sorts = ConvertSorts(SortExpressions) }, out Fault);

				return Silverlight.DataSetConnector.Connector.ToXml(ds);
			} catch (Exception exc) {
				ASPException.Publish(exc);
				Fault = new FaultContract(exc);
				return "";
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Convert sorts. </summary>
		///
		/// <param name="sorts">	The sorts. </param>
		///
		/// <returns>	A list of. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		static Dictionary<string,Dimok.Shared.ListSortDirection> ConvertSorts(Dictionary<string,string> sorts){
			return sorts.ToDictionary(s => s.Key, s => (Dimok.Shared.ListSortDirection)Enum.Parse(typeof(Dimok.Shared.ListSortDirection), s.Value, true));
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a data set object. </summary>
		///
		/// <param name="EntityType">				Type of the entity. </param>
		/// <param name="Columns">					The columns. </param>
		/// <param name="PageType">					Type of the page. </param>
		/// <param name="ControlID">				Identifier for the control. </param>
		/// <param name="ReportName">				Name of the report. </param>
		/// <param name="SortExpressions">	The sort expressions. </param>
		/// <param name="Filters">					The filters. </param>
		/// <param name="StartRow">					The start row. </param>
		/// <param name="RowsCount">				Number of rows. </param>
		/// <param name="Fault">						[out] The fault. </param>
		///
		/// <returns>	The data set object. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		protected static System.Data.DataSet GetDataSetObject(string EntityType,
	 string[] Columns, string PageType, string ControlID, string ReportName,
	Dictionary<string, string> SortExpressions,
	Dictionary<string, string> Filters, int StartRow, int RowsCount, out Dimok.Shared.FaultContract Fault) {
			TDataContext dc = new TDataContext();
			var tableType = dc.GetTableType(EntityType);
			var iq = GetQueryable(tableType, dc, PageType, ControlID, ReportName, SortExpressions, Filters, StartRow, RowsCount, out Fault) as IQueryable;
			if (Columns != null && Columns.Length > 0)
				iq = iq.Select("new (" + string.Join(",", Columns.ToArray()) + ")");
			var ds = iq.GetDataSet(dc, EntityType);
			GetAggregates(dc, tableType, Columns, Filters,t=>t.Name).ForEach(t => ds.Tables.Add(t));
			return ds;
		}
		static readonly PropertyInfo IsAverageMetaProperty = GetIsAverageMetaPropertyDescriptor();
		static readonly PropertyInfo IsTotalMetaProperty = GetIsTotalMetaPropertyDescriptor();

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the is average meta property descriptor. </summary>
		///
		/// <returns>	The is average meta property descriptor. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		static PropertyInfo GetIsAverageMetaPropertyDescriptor() { return GetMetaFieldPropertyDescriptor("IsAverage"); }

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the is total meta property descriptor. </summary>
		///
		/// <returns>	The is total meta property descriptor. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		static PropertyInfo GetIsTotalMetaPropertyDescriptor() { return GetMetaFieldPropertyDescriptor("IsTotal"); }

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a meta field property descriptor. </summary>
		///
		/// <param name="propertyName">	Name of the property. </param>
		///
		/// <returns>	The meta field property descriptor. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		static PropertyInfo GetMetaFieldPropertyDescriptor(string propertyName) {
			var pd = typeof(MetaField).GetProperty(propertyName);
			if (pd == null) throw new MissingMemberException(typeof(MetaField).Name, propertyName);
			return pd;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the aggregates. </summary>
		///
		/// <param name="dataContext">			Context for the data. </param>
		/// <param name="tableType">				Type of the table. </param>
		/// <param name="columns">					The columns. </param>
		/// <param name="filter">						The filter. </param>
		/// <param name="tableNameLambda">	The table name lambda. </param>
		///
		/// <returns>	The aggregates. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		static List<DataTable> GetAggregates(DataContext dataContext, Type tableType, string[] columns, Dictionary<string, string> filter, Func<Type, string> tableNameLambda) {
			var ds = new List<DataTable>();
			ds.AddRange(GetAggregates(dataContext, tableType, columns, filter, tableNameLambda, mf => mf.IsAverage, "AVG"));
			ds.AddRange(GetAggregates(dataContext, tableType, columns, filter, tableNameLambda, mf => mf.IsTotal, "SUM"));
			return ds;
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets the aggregates. </summary>
		///
		/// <param name="dataContext">							Context for the data. </param>
		/// <param name="tableType">								Type of the table. </param>
		/// <param name="columns">									The columns. </param>
		/// <param name="filter">										The filter. </param>
		/// <param name="tableNameLambda">					The table name lambda. </param>
		/// <param name="aggregatePropertyLambda">	The aggregate property lambda. </param>
		/// <param name="aggregateFunctionName">		Name of the aggregate function. </param>
		///
		/// <returns>	The aggregates. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		static List<DataTable> GetAggregates(DataContext dataContext, Type tableType, string[] columns, 
			Dictionary<string, string> filter, Func<Type, string> tableNameLambda, Func<MetaField, bool> aggregatePropertyLambda, string aggregateFunctionName) {
			var tableName = tableNameLambda(tableType);
			var ds = new List<DataTable>();
			var w = RowFilter.Parse(tableType, filter);
			var metaTables = new TMetaTablesData();
			if (metaTables.ContainsKey(tableName)) {
				var metaTable = metaTables[tableName];
				var aggregateFieldNames = (from key in metaTable.Keys.Where(k => aggregatePropertyLambda(metaTable[k]))
								 join c in columns on key equals c
								 select key).ToList();

				var fa = aggregateFieldNames.Select(f => string.Format("{0}({1}){1}", aggregateFunctionName, "[" + f + "]"));
				if (fa.Count() > 0) {
					var dpList = new List<DynamicProperty>();
					aggregateFieldNames.ForEach(f => dpList.Add(new DynamicProperty(f, tableType.GetProperty(f).PropertyType)));
					foreach (var f in filter.Select((kv, i) => new { kv, i }))
						if (!dpList.Any(dp => dp.Name == f.kv.Key))
							dpList.Add(new DynamicProperty(f.kv.Key, w[f.i + 1].GetType()));
					var resultType = System.Linq.Dynamic.DynamicExpression.CreateClass(dpList);
					var sqlAverages = string.Join(",", fa.ToArray());
					var sqlTableName = tableType.GetCustomAttributes(true).OfType<System.Data.Linq.Mapping.TableAttribute>().First().Name;
					var sqlCommandText = "SELECT " + sqlAverages + " FROM " + sqlTableName+" AS t0";
					var parameters = new List<SqlParameter>();

					if (w.Length > 0) {
						var q = dataContext.GetTable(tableType).AsQueryable().Where(w[0] + "", w.Skip(1).ToArray());
						var cmd = dataContext.GetCommand(q);
						//var where = Regex.Replace(Regex.Match(cmd.CommandText, @"\sWHERE\s+[\s\S]+").Value, @"(?:@p(?<d>\d+))", "{${d}}");
						var where = Regex.Match(cmd.CommandText, @"\sWHERE\s+[\s\S]+").Value;
						sqlCommandText += where;
						parameters.AddRange(cmd.Parameters.Cast<SqlParameter>());
						//var a1 = dataContext.ExecuteQuery(resultType, sqlCommand, cmd.Parameters.Cast<SqlParameter>().Select(p=>p.Value).ToArray())
						//  .AsQueryable();
					}
					using (var cmd = new SqlCommand(sqlCommandText,dataContext.Connection as SqlConnection)) {
						cmd.Parameters.Clear();
						parameters.ForEach(p => cmd.Parameters.AddWithValue(p.ParameterName, p.Value));
						using (var da = new SqlDataAdapter(cmd)) {
							var dt = new DataTable(aggregateFunctionName);
							da.Fill(dt);
							ds.Add(dt);
						}
					}
				}
				return ds;
				//Select(k => "AVG(" + k + ")").ToArray();
				//var aggregates = dataContext.GetTable(entityType).AsQueryable().Select("new(Average(APR))");
				//var a = aggregates.Count();
			}
			return ds;
		}
		#endregion

		#region GetEntity ==================

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets an entity. </summary>
		///
		/// <typeparam name="TEntity">	Type of the entity. </typeparam>
		/// <param name="PageType">					Type of the page. </param>
		/// <param name="ControlID">				Identifier for the control. </param>
		/// <param name="ReportName">				Name of the report. </param>
		/// <param name="SortExpressions">	The sort expressions. </param>
		/// <param name="Filters">					The filters. </param>
		/// <param name="Fault">						[out] The fault. </param>
		///
		/// <returns>	The entity< t entity> </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static TEntity[] GetEntity<TEntity>(
			string PageType, string ControlID, string ReportName,
			Dictionary<string, string> SortExpressions,
			Dictionary<string, string> Filters, out Dimok.Shared.FaultContract Fault)
			where TEntity : class {
			return GetEntity<TEntity>(PageType, ControlID, ReportName, SortExpressions, Filters, -1, -1, out Fault);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets an entity. </summary>
		///
		/// <typeparam name="TEntity">	Type of the entity. </typeparam>
		/// <param name="PageType">					Type of the page. </param>
		/// <param name="ControlID">				Identifier for the control. </param>
		/// <param name="ReportName">				Name of the report. </param>
		/// <param name="SortExpressions">	The sort expressions. </param>
		/// <param name="Filters">					The filters. </param>
		/// <param name="StartRow">					The start row. </param>
		/// <param name="RowsCount">				Number of rows. </param>
		/// <param name="Fault">						[out] The fault. </param>
		///
		/// <returns>	The entity< t entity> </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static TEntity[] GetEntity<TEntity>(
			string PageType, string ControlID, string ReportName,
			Dictionary<string, string> SortExpressions,
			Dictionary<string, string> Filters, int StartRow, int RowsCount, out Dimok.Shared.FaultContract Fault)
			where TEntity : class {
			return GetQueryable<TEntity>(new TDataContext(),
				PageType, ControlID, ReportName, SortExpressions, Filters, StartRow, RowsCount, out Fault).ToArray();
		}
		#endregion

		#region GetQueryable =======================

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a queryable. </summary>
		///
		/// <typeparam name="TEntity">	Type of the entity. </typeparam>
		/// <param name="dataContext">			Context for the data. </param>
		/// <param name="PageType">					Type of the page. </param>
		/// <param name="ControlID">				Identifier for the control. </param>
		/// <param name="ReportName">				Name of the report. </param>
		/// <param name="SortExpressions">	The sort expressions. </param>
		/// <param name="Filters">					The filters. </param>
		/// <param name="StartRow">					The start row. </param>
		/// <param name="RowsCount">				Number of rows. </param>
		/// <param name="Fault">						[out] The fault. </param>
		///
		/// <returns>	The queryable< t entity> </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static IQueryable<TEntity> GetQueryable<TEntity>(
			DataContext dataContext,
			string PageType, string ControlID, string ReportName,
			Dictionary<string, string> SortExpressions,
			Dictionary<string, string> Filters, int StartRow, int RowsCount, out Dimok.Shared.FaultContract Fault)
			where TEntity : class {
			var q = GetQueryable(typeof(TEntity), dataContext, PageType, ControlID, ReportName, SortExpressions, Filters,
				StartRow, RowsCount, out Fault);
			return q == null ? null : q.Cast<TEntity>();
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a queryable. </summary>
		///
		/// <param name="entityType">				Type of the entity. </param>
		/// <param name="dataContext">			Context for the data. </param>
		/// <param name="PageType">					Type of the page. </param>
		/// <param name="ControlID">				Identifier for the control. </param>
		/// <param name="ReportName">				Name of the report. </param>
		/// <param name="SortExpressions">	The sort expressions. </param>
		/// <param name="Filters">					The filters. </param>
		/// <param name="StartRow">					The start row. </param>
		/// <param name="RowsCount">				Number of rows. </param>
		/// <param name="Fault">						[out] The fault. </param>
		///
		/// <returns>	The queryable. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public static IQueryable GetQueryable(Type entityType,
			DataContext dataContext,
			string PageType, string ControlID, string ReportName,
			Dictionary<string, string> SortExpressions,
			Dictionary<string, string> Filters, int StartRow, int RowsCount, out Dimok.Shared.FaultContract Fault)
			{
			Fault = null;
			try {
				Filters = Filters.Trim();
				var c = from f in Filters.Keys
								join p in entityType.GetProperties() on f equals p.Name into fields
								from fg in fields.DefaultIfEmpty()
								where fg == null
								select f;
				if (c.Count() > 0) Fault = new Dimok.Shared.FaultContract(null, "Field " + string.Join(",", c.ToArray()) + " does not exist in " + entityType.Name);
				//var where = Filtering.RowFilter.Parse<Orders_Qry>(Filters);
				var w = RowFilter.Parse(entityType, Filters);
				//return dc.Orders_Qries.Where("OrderDate>DateTime.Parse(@0)", "5/1/1998").ToArray();
				//var o = dc.Orders_Qries.Where(where);
				var o = dataContext.GetTable(entityType).AsQueryable();
				if (w.Length > 0) o = o.Where(w[0] + "", w.Skip(1).ToArray());
				if (SortExpressions.Count > 0)
					o = o.OrderBy(string.Join(",", SortExpressions.Select(se => se.Key + " " + se.Value).ToArray()));
				if (StartRow > 0) o = o.Skip(StartRow - 1);
				if (RowsCount >= 0) o = o.Take(RowsCount);
				return o;
			} catch (Exception exc) {
				Fault = new Dimok.Shared.FaultContract(exc);
				return null;
			}
		}
		#endregion

		#region Profile ====================

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a profile filters. </summary>
		///
		/// <param name="entityTypeName">	Name of the entity type. </param>
		/// <param name="filters">				The filters. </param>
		///
		/// <returns>	The profile filters. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		static Dictionary<string, Dimok.Shared.Filtering.FilterQuery> GetProfileFilters(string entityTypeName, Dictionary<string, string> filters) {
			return GetProfileFilters(new TDataContext().GetTableType(entityTypeName), filters);
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a profile filters. </summary>
		///
		/// <param name="entityType">	Type of the entity. </param>
		/// <param name="filters">		The filters. </param>
		///
		/// <returns>	The profile filters. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		static Dictionary<string, Dimok.Shared.Filtering.FilterQuery> GetProfileFilters(Type entityType, Dictionary<string, string> filters) {
			return
				filters.ToDictionary(f => f.Key,
				f => RowFilter.GetFilterQuery(f.Value, entityType.GetProperty(f.Key).PropertyType));
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Saves a layout. </summary>
		///
		/// <param name="PageType">		Type of the page. </param>
		/// <param name="ControlID">	Identifier for the control. </param>
		/// <param name="Profile">		The profile. </param>
		/// <param name="Fault">			[out] The fault. </param>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public void SaveLayout(string PageType, string ControlID, ReportProfile Profile , out FaultContract Fault) {
			Fault = null;
			try{
				var p = new TProfiler().Initialize(PageType, ControlID);
				var report = p.Report(Profile.ReportName);
				if (Profile.Filters != null) p.Filters_Add(Profile.ReportName, GetProfileFilters(new TDataContext().GetTableType(ControlID), Profile.Filters));
				if (Profile.Columns != null) report.Columns = string.Join(",", Profile.Columns);
				if (Profile.Docks != null) report.RadDocks = Profile.Docks;
				if (Profile.ShowFilter.HasValue) report.ShowFilter = Profile.ShowFilter.Value;
				if (Profile.ShowTotals.HasValue) report.ShowTotals = Profile.ShowTotals.Value;
				if (Profile.ShowAverages.HasValue) report.ShowAverages = Profile.ShowAverages.Value;
				if (Profile.Sorts != null) p.Sorts_Add(Profile.ReportName, Profile.Sorts);
				//if (ReportName == "") Profiler.Details_Sync();
				p.Save(Profile.ReportName);
			} catch (Exception exc) {
				Dimok.Exceptions.ASPException.Publish(exc);
				Fault = new FaultContract(exc);
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Gets a profile. </summary>
		///
		/// <param name="PageType">		Type of the page. </param>
		/// <param name="ControlID">	Identifier for the control. </param>
		/// <param name="ReportName">	Name of the report. </param>
		/// <param name="Fault">			[out] The fault. </param>
		///
		/// <returns>	The profile. </returns>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public ReportProfile GetProfile(string PageType, string ControlID, string ReportName,out Dimok.Shared.FaultContract Fault) {
      Fault = null;
      try {
        var p = new TProfiler().Initialize(PageType, ControlID);
				if (ReportName + "" == "") ReportName = p.Grid.LastReportName + "";
        var report = p.Report(ReportName);
        return new ReportProfile() {
          Filters = report.FilterQueries.ToDictionary(f => f.Key, f => f.Value.Query),
          Sorts = report.Sorts.ToDictionary(s => s.Key, s => s.Value.Order),
          Columns = (report.Columns ?? "").Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(),
          MetaTables = new TMetaTablesData(),
					Docks = report.RadDocks,
					ShowFilter = report.ShowFilter,
					ShowTotals = report.ShowTotals,
					ShowAverages = report.ShowAverages,
          ReportName = ReportName,
					ReportNames = p.ReportNames()
        };
        //return report.Sorts.Select(s => s.Key + "," + s.Value.Order).ToArray();
      } catch (Exception exc) {
				Dimok.Exceptions.ASPException.Publish(exc);
				Fault = new FaultContract(exc);
        return null;
      }
    }

		////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>	Removes the profile. </summary>
		///
		/// <param name="PageType">		Type of the page. </param>
		/// <param name="ControlID">	Identifier for the control. </param>
		/// <param name="ReportName">	Name of the report. </param>
		/// <param name="Fault">			[out] The fault. </param>
		////////////////////////////////////////////////////////////////////////////////////////////////////

		public void RemoveProfile(string PageType, string ControlID, string ReportName, out Dimok.Shared.FaultContract Fault) {
			Fault = null;
			try {
				var p = new TProfiler().Initialize(PageType, ControlID);
				p.Grid.Reports.Remove(ReportName);
				p.Save();
			} catch (Exception exc) {
				Dimok.Exceptions.ASPException.Publish(exc);
				Fault = new FaultContract(exc);
			}
		}
		#endregion

		#endregion
	}
}
